﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Text;
using System.Windows.Forms;

namespace Qreed.Diagnostics
{
    /// <summary>
    /// The base logger class.
    /// </summary>
    public partial class BaseLogger : Component
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="BaseLogger"/> class.
        /// </summary>
        public BaseLogger()
        {
            InitializeComponent();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="BaseLogger"/> class.
        /// </summary>
        /// <param name="container">The container.</param>
        public BaseLogger(IContainer container)
        {
            container.Add(this);

            InitializeComponent();
        }

        /// <summary>
        /// Gets or sets the invoke control.
        /// </summary>
        /// <value>The invoke control.</value>
        public Control InvokeControl { get; set; }

        private LogLevel _level = LogLevel.Info;
        /// <summary>
        /// Gets or sets the log level.
        /// </summary>
        /// <value>The log level.</value>
        [DefaultValue(typeof(LogLevel), "Info")]
        [Category("Logging")]
        [NotifyParentProperty(true)]
        public LogLevel Level
        {
            get { return this._level; }
            set
            {
                LoggerLevelChange e = new LoggerLevelChange(_level, value);
                this._level = value;

                OnLevelChanged(e);
            }
        }

        private bool _includeStackFrame;
        /// <summary>
        /// Gets or sets a value indicating whether to include the calling function name in the logging.
        /// </summary>
        /// <value>
        /// 	<c>true</c> to include the calling function name; otherwise, <c>false</c>.
        /// </value>
        [DefaultValue(false)]
        [Category("Logging")]
        [NotifyParentProperty(true)]
        public bool IncludeStackFrame
        {
            get { return this._includeStackFrame; }
            set 
            { 
                this._includeStackFrame = value;

                if (IncludeStackFrameChanged != null)
                    IncludeStackFrameChanged(this, null);
            }
        }

        /// <summary>
        /// Raises the <see cref="LevelChanged"/> event.
        /// </summary>
        /// <param name="e">The event arguments.</param>
        protected virtual void OnLevelChanged(LoggerLevelChange e)
        {
            if (LevelChanged != null)
                LevelChanged(this, e);
        }

        /// <summary>
        /// Occurs when the logger writes message a message to the log.
        /// </summary>
        [Description("Occurs when the logger writes message a message to the log.")]
        public event EventHandler<LoggerEventArgs> WriteMessage;

        /// <summary>
        /// Occurs when <see cref="Level"/> has changed.
        /// </summary>
        [Description("Occurs when LogLevel has changed.")]
        public event EventHandler<LoggerLevelChange> LevelChanged;

        /// <summary>
        /// Occurs when the IncludeCallingFunctionName has changed.
        /// </summary>
        [Description("Occurs when IncludeCallingFunctionNameChanged has changed.")]
        public event EventHandler IncludeStackFrameChanged;

        /// <summary>
        /// Writes the specified message.
        /// </summary>
        /// <param name="message">The message.</param>
        public void Write(string message)
        {
            _Write(message, null, null, LogLevel.Info);
        }

        /// <summary>
        /// Writes the specified message if the current build is DEBUG.
        /// </summary>
        /// <param name="message">The message.</param>
        [Conditional("DEBUG")]
        public void DebugWrite(string message)
        {
            _Write(message, null, null, LogLevel.Debug);
        }

        /// <summary>
        /// Writes the specified message.
        /// </summary>
        /// <param name="message">The message.</param>
        /// <param name="details">The details.</param>
        public void Write(string message, string details)
        {
            _Write(message, details, null, LogLevel.Info);
        }

        /// <summary>
        /// Writes the specified message if the current build is DEBUG.
        /// </summary>
        /// <param name="message">The message.</param>
        /// <param name="details">The details.</param>
        [Conditional("DEBUG")]
        public void DebugWrite(string message, string details)
        {
            _Write(message, details, null, LogLevel.Debug);
        }

        /// <summary>
        /// Writes the specified message if the current build is DEBUG.
        /// </summary>
        /// <param name="message">The message.</param>
        /// <param name="detailTarget">The detail target to dump the properties from.</param>
        [Conditional("DEBUG")]
        public void DebugWrite(string message, object detailTarget)
        {
            string details = DiagnosticsHelper.DebugPropertiesToString(detailTarget);
            _Write(message, details, null, LogLevel.Debug);
        }

        /// <summary>
        /// Writes the specified message.
        /// </summary>
        /// <param name="message">The message.</param>
        /// <param name="level">The level.</param>
        public void Write(string message, LogLevel level)
        {
            _Write(message, null, null, level);
        }

        /// <summary>
        /// Writes the specified message.
        /// </summary>
        /// <param name="message">The message.</param>
        /// <param name="details">The details.</param>
        /// <param name="level">The level.</param>
        public void Write(string message, string details, LogLevel level)
        {
            _Write(message, details, null, level);
        }

        /// <summary>
        /// Writes the specified message.
        /// </summary>
        /// <param name="message">The message.</param>
        /// <param name="ex">The exception.</param>
        public void Write(string message, Exception ex)
        {
            _Write(message, null, ex, LogLevel.Error);
        }

        /// <summary>
        /// Writes the specified message.
        /// </summary>
        /// <param name="message">The message.</param>
        /// <param name="details">The details.</param>
        /// <param name="ex">The exception.</param>
        public void Write(string message, string details, Exception ex)
        {
            _Write(message, details, ex, LogLevel.Error);
        }
        
        /// <summary>
        /// Writes the specified exception.
        /// </summary>
        /// <param name="ex">The exception.</param>
        public void Write(Exception ex)
        {
            _Write(null, null, ex, LogLevel.Error);
        }

        private void _Write(string message, string details, Exception ex, LogLevel level)
        {
            if (level < Level)
                return;

            if (string.IsNullOrEmpty(message))
            {
                if (!string.IsNullOrEmpty(details))
                    message = details;
                else if(ex != null)
                    message = ex.Message;
            }

            if (string.IsNullOrEmpty(details) && ex != null)
                details = ex.ToString();

            StackFrame stackFrame = null;

            if (IncludeStackFrame)
                stackFrame = new StackFrame(2, true);

            LoggerEventArgs e = new LoggerEventArgs(message, details, ex, level, stackFrame);

            OnWriteMessage(e);
        }

        /// <summary>
        /// Raises the <see cref="WriteMessage"/> event.
        /// </summary>
        /// <param name="baseLogger">The base logger.</param>
        /// <param name="e">The <see cref="Qreed.Diagnostics.LoggerEventArgs"/> instance containing the event data.</param>
        protected virtual void OnWriteMessage(LoggerEventArgs e)
        {
            try
            {
                if (WriteMessage != null)
                {
                    if (InvokeControl != null &&
                       InvokeControl.InvokeRequired)
                    {
                        InvokeControl.BeginInvoke(WriteMessage, new object[] { this, e });
                    }
                    else
                        WriteMessage(this, e);
                }
            }
            catch (InvalidOperationException ex)
            {
                throw (new InvalidOperationException("Exception occured while executing the WriteMessage event handler. Make sure the InvokeControl property is set if this is caused by cross threading issues.", ex));
            }
        }
    }
}
